﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

using zijian666.Abstractions;
using zijian666.Core;

namespace zijian666.Filters;

public class CustomOperatorFilter<T> : IConvertFilter<T>
{
    readonly Dictionary<Type, Func<object, T>?> _convertFns = [];
    public ConvertResult<T> OnConverting(IConvertContext context, IConvertor<T> convertor, object? value)
    {
        if (value is null)
        {
            return convertor is IFromNull<T> fromNull ? fromNull.FromNull(context) : ConvertResult<T>.FAIL;
        }
        if (!_convertFns.TryGetValue(value.GetType(), out var fn))
        {
            var method = CompileMethod(value.GetType());
            lock (_convertFns)
            {
                if (method is null)
                {
                    _convertFns[value.GetType()] = null;
                }
                else
                {
                    _convertFns[value.GetType()] = fn = method;
                }
            }
        }
        return fn is null ? ConvertResult<T>.FAIL : fn(value);
    }

    public void OnConverted(IConvertContext context, IConvertor<T> convertor, object? value, ConvertResult<T> result)
    {

    }

    public void OnException(IConvertContext context, IConvertor<T> convertor, object? value, Exception exception)
    {

    }

    /// <summary>
    /// 编译转换方法, 优先级 输出类型的强制转换 > 输出类型的隐式转换 > 输入类型的强制转换 > 输入类型的隐式转换
    /// </summary>
    private static Func<object, T>? CompileMethod(Type inType)
    {
        var outType = typeof(T);
        const BindingFlags flags = BindingFlags.Static | BindingFlags.Public;

        var method = outType.GetMethods(flags).Union(inType.GetMethods(flags))
                            .Where(x => x.Name is "op_Explicit" or "op_Implicit")
                            .Where(x => x.ReturnType == outType && x.GetParameters() is var p && p is { Length: 1 } && p[0].ParameterType == inType)
                            .FirstOrDefault();
        if (method is null)
        {
            return null;
        }

        var parameters = method.GetParameters();
        var p = Expression.Parameter(typeof(object), "p");
        var args = Expression.Convert(p, parameters[0].ParameterType);
        var call = Expression.Call(method, args);
        return (Func<object, T>)Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(object), outType), call, p).Compile();
    }
}
